package com.example.licslan.contorller;

import com.example.licslan.dao.UserDao;
import com.example.licslan.entity.User;
import com.example.licslan.task.TaskThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


/**
 * @author WEILIN
 *
 *
 * 为了方便测试 调用关系就写的比较简单
 */
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    private final UserDao userDao;

    public UserController(UserDao userDao) {
        this.userDao = userDao;
    }

    @RequestMapping("/userList")
    public List<User> getUserList(){
        return userDao.findAllUser();
    }
    @RequestMapping("/add")
    public String addUser(@RequestBody User user){
        if(user!=null){
            userDao.createUser(user);
            return "success";
        }else{
            return "error";
        }
    }

    /**
     * 做实验  实验目的
     * 运用线程池  多线程实验
     * */
    @RequestMapping("/addList1")
    public String addList1(@RequestBody List<User> userList){
        if(userList.size()>0){
            long startTime = System.currentTimeMillis();
            spilt2MultiThread(userList, 2500);
            long endTime = System.currentTimeMillis();
            log.info("多线程统计用时："+(endTime-startTime)+" 毫秒");
            return "success";
        }else {
            return "error";
        }
    }



    /**
     * 单线程实验
     * */
    @RequestMapping("/addList2")
    public String addList2(@RequestBody List<User> userList){
        if(userList.size()>0){
            long startTime = System.currentTimeMillis();
            Long aLong = userDao.creatUserList(userList);
            log.info("写入"+aLong+"条");
            long endTime = System.currentTimeMillis();
            log.info("单线程统计用时："+(endTime-startTime)+" 毫秒");
            return "success";
        }else {
            return "error";
        }
    }



    /**
     * 做实验  实验目的
     * 运用线程池  多线程实验
     * */
    @RequestMapping("/addListBatch1")
    public String addListBatch1(){
            long startTime = System.currentTimeMillis();
            spilt2MultiThread(getList(1000000), 2500);
            long endTime = System.currentTimeMillis();
            log.info("多线程统计用时："+(endTime-startTime)+" 毫秒");
            return "success";
    }

    /**
     * 做实验  实验目的  1000
     * 单线程
     * */
    @RequestMapping("/addListBatch2")
    public String addListBatch2(){
        long startTime2 = System.currentTimeMillis();
        Long aLong = userDao.creatUserList(getList(1000000));
        log.info("写入"+aLong+"条");
        long endTime2 = System.currentTimeMillis();
        log.info("单线程统计用时："+(endTime2-startTime2)+" 毫秒");
        return "success";
    }

    /**
     * 多线程实现
     * */
    private void spilt2MultiThread(List<User> userList, int batchSize){

        //FIXME 思考 这里线程池可以 尝试换成其他的方式来测试?  底层好几种类型  可以结合业务场景经过不断测试和优化 选择最优的


        /**
         *
         * Java通过Executors提供四种线程池，分别为：
         * 1、newCachedThreadPool：创建一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，
         * 若无可回收，则新建线程。（线程最大并发数不可控制）；线程池为无限大，当执行第二个任务时若第一个任务已经完成，
         * 会复用执行第一个任务的线程，而不用每次新建线程。
         * 2、newFixedThreadPool：创建一个定长线程池，可控制线程最大并发数，超出的线程会在队列中等待。
         * 3、newScheduledThreadPool：创建一个定长线程池，支持定时及周期性任务执行、延迟执行。
         * 4、newSingleThreadExecutor：创建一个单线程化的线程池，它只会用唯一的工作线程来执行任务，
         * 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
         * **/


        /*ExecutorService executorService = Executors.newCachedThreadPool();*/
        ExecutorService executorService = Executors.newCachedThreadPool();
        /*ExecutorService executorService = Executors.newFixedThreadPool(8);*/

        //装执行结果集合
        List<Future<Long>> resultList = new ArrayList<>();

        int size;

        if(userList.size() < batchSize && userList.size()/batchSize==0){
            size =1;
        }else {
            size =userList.size()%batchSize > 0 ? (1+userList.size()/batchSize) : userList.size()/batchSize;
        }

        //切分批次
        for (int i = 0; i < size; i++) {
            LinkedList<User> uList;
            //最后一个时
            if(i == size -1){
                //由于实现的不同，ArrayList和LinkedList在这个方法上存在一定的性能差异，
                //由于ArrayList是基于数组实现的，而数组是一块连续的内存空间，
                //如果在数组的任意位置插入元素，必然导致在该位置后的所有元素需要重新排列，因此，其效率相对会比较低
                uList = new LinkedList<>(userList.subList(i * batchSize, userList.size()));
            }else{
                uList = new LinkedList<>(userList.subList(i * batchSize, (i + 1) * batchSize));
            }

            if(uList.size() > 0){
                Future<Long> future = executorService.submit(new TaskThread(userDao,uList));
                //将任务执行结果存储到List中
                resultList.add(future);
            }

        }
        //遍历任务的结果
        for (Future<Long> fs : resultList) {
            try {
                log.info("每次写入数据库多少[ "+fs.get()+" ]条");
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            } finally{
                executorService.shutdown();
            }
        }
    }


    /**
     * 产生大量数据
     * */
    public static List<User> getList(int size){
        List<User> userList = new LinkedList<>();
        //随机数产生
        Random random = new Random();
        for (int i = 0; i < size; i++) {

            User user = new User(String.valueOf((i+1)),
                    random.nextInt(10)+"name");

            userList.add(user);
        }
        log.info("getList 产生了[" + userList.size()+"]条数据");

        return userList;

    }

    /**
     * 建表语句
     * DROP TABLE IF EXISTS `user`;
     *
     * CREATE TABLE `user` (
     *   `id` int(11) NOT NULL,
     *   `username` varchar(16) DEFAULT NULL,
     *   PRIMARY KEY (`id`)
     * ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
     * */

    /**
     * 实验结果  MySQL < 1c 2g
     *
     * ExecutorService executorService = Executors.newCachedThreadPool()
     * 相同条件下面 重启服务 写入数据 重建表  再写入数据对比
     * 1000条 首次  测试1000写入
     * 实验结果如下：
     * 单线程：
     * 写入1000条
     * 单线程统计用时：1746 毫秒
     * 多线程：
     * 每次写入数据库多少[ 1000 ]条
     * 多线程统计用时：1136 毫秒
     * ====================================
     * 5000条 第二次  测试5000写入
     * 单线程:
     * 写入5000条
     * 单线程统计用时：1344 毫秒
     *
     * 多线程：
     * 每次写入数据库多少[ 2500 ]条
     * 每次写入数据库多少[ 2500 ]条
     * 多线程统计用时：1809 毫秒
     * ====================================
     * 50000条 第三次  测试5万写入
     * 单线程:
     * 写入50000条
     * 单线程统计用时：2857 毫秒
     * 多线程:
     * 每次写入数据库多少[ 2500 ]条
     * ........
     * 每次写入数据库多少[ 2500 ]条
     * 多线程统计用时：2360 毫秒
     * 上面已经有了约500毫秒差距  多线程快 500毫秒
     * ====================================
     * 500000条 第四次  测试50万条数据写入
     * 单线程：
     * 写入500000条
     * 单线程统计用时：23786 毫秒  约 24秒钟
     * 多线程：
     * 每次写入数据库多少[ 2500 ]条
     * 每次写入数据库多少[ 2500 ]条
     * ............
     * 每次写入数据库多少[ 2500 ]条
     * 每次写入数据库多少[ 2500 ]条
     * 多线程统计用时：9021 毫秒  约 9 秒  比上面的24秒快15秒了 ！！！
     * 其实可以尝试提高每次写入的数据batchSize 当前2500
     * 但是 mybatis批量写入一次好像是有限制的 忘记了多少 可以查一下
     * 下面调整为batchSize = 10000
     * 每次写入数据库多少[ 10000 ]条
     * ......
     * 每次写入数据库多少[ 10000 ]条
     * 多线程统计用时：9425 毫秒
     * 下面调整为batchSize = 5000
     * 每次写入数据库多少[ 5000 ]条
     * ..........
     * 每次写入数据库多少[ 5000 ]条
     * 多线程统计用时：9620 毫秒
     * 对比发现  2500  5000 10000 时  时间差不多了
     * ====================================
     * 1000000条 第五次  测试100万条数据写入  此时batchSize恢复为 2500
     * 单线程:
     * 写入1000000条
     * 单线程统计用时：44271 毫秒  约44秒
     * 多线程：
     * 每次写入数据库多少[ 2500 ]条
     * 每次写入数据库多少[ 2500 ]条
     * .........
     * 每次写入数据库多少[ 2500 ]条
     * 多线程统计用时：15062 毫秒 约 15秒
     * 
     * ExecutorService executorService = Executors.newFixedThreadPool(16);
     * 每次写入数据库多少[ 2500 ]条
     * 每次写入数据库多少[ 2500 ]条
     * 多线程统计用时：16549 毫秒  16秒
     * ExecutorService executorService = Executors.newFixedThreadPool(8);
     * 多线程统计用时：15350 毫秒 15.3秒
     * ====================================
     * 10000000条 第六次  测试1000万条数据写入  此时batchSize恢复为 2500
     * 多线程:
     *
     * */

}
